{This file is part of CheLive.

CheLive is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Foobar.  If not, see <http://www.gnu.org/licenses/>.}

{Реализует работу с вводом}
unit CoreInput;

interface

uses
{$IFDEF WIN32}
	Windows,
{$ENDIF}
{$IFDEF LINUX}
	xlib,
{$ENDIF}
	SysUtils, CoreTypes, CoreMath, CoreDisplay, CoreWindow, CoreOpenGL;

const
	IK_BACKSPACE = 8;
	IK_TAB = 9;
	IK_SHIFT = 16;
	IK_CONTROL = 17;
	IK_ALT = 18;
	IK_PAUSE = 19;
	IK_CAPITAL = 20;
	IK_ESCAPE = 27;
	IK_SPACE = 32;
	IK_ENTER = 13;
	IK_PAGEUP = 33;
	IK_PAGEDOWN = 34;
	IK_END = 35;
	IK_HOME = 36;
	IK_LEFT = 37;
	IK_UP = 38;
	IK_RIGHT = 39;
	IK_DOWN = 40;
	IK_SNAPSHOT = 44;
	IK_INSERT = 45;
	IK_DELETE = 46;
	IK_MULTIPLY = 106;
	IK_ADD = 107;
	IK_SEPARATOR = 108;
	IK_SUBSTRUCT = 109;
	IK_DECIMAL = 110;
	IK_DIVIDE = 111;
	IK_TYLDA = 192;

	IK_F1 =  112;
	IK_F2 =  113;
	IK_F3 =  114;
	IK_F4 =  115;
	IK_F5 =  116;
	IK_F6 =  117;
	IK_F7 =  118;
	IK_F8 =  119;
	IK_F9 =  120;
	IK_F10 = 121;
	IK_F11 = 122;
	IK_F12 = 123;

	IK_0 = 48;
	IK_1 = 49;
	IK_2 = 50;
	IK_3 = 51;
	IK_4 = 52;
	IK_5 = 53;
	IK_6 = 54;
	IK_7 = 55;
	IK_8 = 56;
	IK_9 = 57;

	IK_A = 65;
	IK_B = 66;
	IK_C = 67;
	IK_D = 68;
	IK_E = 69;
	IK_F = 70;
	IK_G = 71;
	IK_H = 72;
	IK_I = 73;
	IK_J = 74;
	IK_K = 75;
	IK_L = 76;
	IK_M = 77;
	IK_N = 78;
	IK_O = 79;
	IK_P = 80;
	IK_Q = 81;
	IK_R = 82;
	IK_S = 83;
	IK_T = 84;
	IK_U = 85;
	IK_V = 86;
	IK_W = 87;
	IK_X = 88;
	IK_Y = 89;
	IK_Z = 90;

	IK_MOUSE_L = 256;
	IK_MOUSE_M = 257;
	IK_MOUSE_R = 258;
	
	{Общее кол-во кнопок IK_MAX - 1}
	IK_MAX = 259;

type
	{Процедура-обработчик события нажатия кнопки в то время когда ввод монополизирован @seealso(TInput.Lock)}
	TInputLockKeyProc = procedure(key: Integer; down: Boolean);
	
	{Структура хранящая параметры монополизации ввода @seealso(TInput.Lock)
	@member(locked True если ввод понополизирован)
	@member(key адрес процедуры-оброботчика события нажатия кнопки)}
	TInputLock = record
		locked: Boolean;
		key: TInputLockKeyProc;
	end;
	
	{Ввод
	@member(keys массив состояник кнопок, True если кнопка нажата)
	@member(prevKeys массив состояник кнопок предидущей итерации, True если кнопка нажата)
	@member(mousePos положение курсора относительно клиентской области окна)
	@member(prevMousePos положение курсора относительно клиентской области окна предидущей итерации)
	@member(mouseDelta смещение положения курсора относительно предидущей итерации)
	@member(mouseCPos положение курсора относительно клиентской области окна на холсте OpenGL @seealso(TOpenGL.canvas))
	@member(mouseCDelta смещение положения курсора относительно предидущей итерации на холсте OpenGL)
	@member(mousing если True то курсор прячется, каждую итерацию расчитывается mouseDelta и затем курсор перемещается в центр окна;
		иначе курсор показывается и каждую итерацию просто расчитывается mouseDelta)
	@member(lock_ параметры монополизации ввода)}
	TInput = class
		keys, prevKeys: array[0..IK_MAX - 1] of Boolean;
		mousePos, prevMousePos, mouseDelta: TVec2;
		mouseCPos, mouseCDelta: TVec2;
		mousing: Boolean;
		lock_: TInputLock;
		
		{Инициализация}
		procedure Init;
		{Обновление состояний (вызывается после каждой итерации приложения)}
		procedure Update;
		{Монополизация ввода
		@param(key адрес процедуры-обработчика события нажатия кнопки)
		@param(lockAnyway если True то ввод будет монополизирован даже если он уже монополизирован)
		@returns(True если ввод был монополизирован)}
		function Lock(key: TInputLockKeyProc; lockAnyway: Boolean): Boolean;
		{Отмена монополизации ввода}
		procedure Unlock;

		{Проверка нажатия любой кнопки
		@returns(True если нажата хотя бы одна кнопка)}
		function AnyKeyDown: Boolean;
		{Проверка состояния кнопки
		@param(key код кнопки (IK_BACKSPACE,...))
		@returns(True если кнопка нажата)}
		function KeyDown(key: Integer): Boolean;
		{Проверка состояния кнопки
		@param(key код кнопки (IK_BACKSPACE,...))
		@returns(True если кнопка нажата в этой итерации, но не была нажата в предидущей)}
		function KeyPress(key: Integer): Boolean;
		{Проверка состояния кнопки
		@param(key код кнопки (IK_BACKSPACE,...))
		@returns(True если кнопка не нажата в этой итерации, но была нажата в предидущей)}
		function KeyRelease(key: Integer): Boolean;
		{Изменение метода управления мышью
		@(mousing_ метод обзора @seealso(TInput.mousing))}
		procedure SetMousing(mousing_: Boolean);

		constructor Create;
	end;

var
	inp: TInput;

implementation

uses
	CoreConsole;

constructor TInput.Create;
begin
	FillChar(keys[0], SizeOf(keys), 0);
	FillChar(prevKeys[0], SizeOf(prevKeys), 0);

	mousing := False;

	Unlock;
end;

procedure HandleEvents(event: PWindowEvent);
begin
	case event^.type_ of
		WND_KEY:
		begin
			if inp.lock_.locked then
			begin
				inp.lock_.key(event^.key.key, event^.key.down);
			end else
			begin
				inp.keys[event^.key.key] := event^.key.down;
			end;
		end;
		
		WND_MOTION: inp.mousePos := event^.motion.pos;
	end;
end;

procedure TInput.Init;
begin
	wnd.AddProc(@HandleEvents);
end;

procedure TInput.Update;
{$IFDEF WIN32}
var
	pnt: TPoint;
{$ENDIF}
begin
	Move(keys[0], prevKeys[0], SizeOf(keys));
	
	mouseCPos := Vec2(mousePos.x / wnd.clientSize.x * ogl.canvas.x, mousePos.y / wnd.clientSize.y * ogl.canvas.y);
	
	mouseDelta := mousePos - prevMousePos;
	mouseCDelta := Vec2(mouseDelta.x / wnd.clientSize.x * ogl.canvas.x, mouseDelta.y / wnd.clientSize.y * ogl.canvas.y);

	prevMousePos := mousePos;

	if mousing then
	begin
		prevMousePos := wnd.clientSize / 2;
	
	{$IFDEF WIN32}
		pnt.x := Round(prevMousePos.x);
		pnt.y := Round(prevMousePos.y);
		
		ClientToScreen(wnd.wnd, pnt);
		SetCursorPos(pnt.x, pnt.y);
	{$ENDIF}
	{$IFDEF LINUX}
		XWarpPointer(disp.disp, wnd.wnd, wnd.wnd, 0, 0, Round(wnd.clientSize.x), Round(wnd.clientSize.y),
			Round(prevMousePos.x), Round(prevMousePos.y));
		XSync(disp.disp, False);
	{$ENDIF}
	end;
end;

function TInput.Lock(key: TInputLockKeyProc; lockAnyway: Boolean): Boolean;
begin
	Result := False;

	if lock_.locked and not lockAnyway then
	begin
		//error
		Exit;
	end;

	lock_.locked := True;
	lock_.key := key;

	Result := True;
end;

procedure TInput.Unlock;
begin
	lock_.locked := False;
	lock_.key := nil;
end;

function TInput.AnyKeyDown: Boolean;
var
	i: Integer;
begin
	Result := False;

	for i := 0 to IK_MAX - 1 do
	begin
		if keys[i] then
		begin
			Result := True;
			Exit;
		end;
	end;
end;

function TInput.KeyDown(key: Integer): Boolean;
begin
	Result := keys[key];
end;

function TInput.KeyPress(key: Integer): Boolean;
begin
	Result := keys[key] and not prevKeys[key];
end;

function TInput.KeyRelease(key: Integer): Boolean;
begin
	Result := not keys[key] and prevKeys[key];
end;

procedure TInput.SetMousing(mousing_: Boolean);
{$IFDEF WIN32}
var
	pnt: TPoint;
{$ENDIF}
begin
	wnd.ShowCursor(not mousing_);
	
	mousing := mousing_;
	
	if mousing then
	begin
		inp.mousePos := wnd.clientSize / 2;
		inp.prevMousePos := inp.mousePos;
	
	{$IFDEF WIN32}
		pnt.x := Round(prevMousePos.x);
		pnt.y := Round(prevMousePos.y);
		
		ClientToScreen(wnd.wnd, pnt);
		SetCursorPos(pnt.x, pnt.y);
	{$ENDIF}
	{$IFDEF LINUX}
		XWarpPointer(disp.disp, wnd.wnd, wnd.wnd, 0, 0, Round(wnd.clientSize.x), Round(wnd.clientSize.y),
			Round(inp.prevMousePos.x), Round(inp.prevMousePos.y));
		XSync(disp.disp, False);
	{$ENDIF}
	end;
end;

end.
